﻿using System;
using DKE_Kinect.Entwicklung.Core;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace DKE_Kinect.Entwicklung.Grafik.ControlManaging
{
	/// <summary>
	/// Verwaltet die Steuerelemente auf dem Bildschirm.
	/// </summary>
	public sealed class ContainerManager : IContainer
	{
		#region Fields

		private GraphicsDevice pDevice;
		private GameWindow pWindow;
		//private SpriteBatch pSprite;

		private ControlCollection pControls;

		private readonly Point pLocation;
		private Point pSize;
		private Rectangle pBounds;

		private Control pPrevControl;
		private bool pMouseDown;

		private Vector2 pMousePrecise;
		private Point pMousePosition;

		private Vector2 pScale;

		private PainectMenu pMainMenu;
		private QuickMenu pQuickMenu;
		private ColorMenu pColorMenu;
        private ProjectMenu pProjectMenu;

		#endregion

		#region Constructor

		/// <summary>
		/// Instanziiert ein neues Objekt dieser Klasse.
		/// </summary>
		/// <param name="Graphics">Graphic Device</param>
		/// <param name="Window">Aktuelles Fenster</param>
		/// <param name="Sprite"> SpriteBatch </param>
		public ContainerManager (GraphicsDevice Graphics, GameWindow Window, SpriteBatch Sprite)
		{
			if ((Graphics == null) || (Window == null) || (Sprite == null))
				throw new NullReferenceException();

			this.pDevice = Graphics;
			this.pWindow = Window;
			//	this.pSprite = Sprite;
			this.pControls = new ControlCollection(this);
			this.pLocation = Point.Zero;
			this.pSize = new Point(this.pWindow.ClientBounds.Width, this.pWindow.ClientBounds.Height);
			this.pBounds = new Rectangle(0, 0, this.pSize.X, this.pSize.Y);

			this.pPrevControl = null;
			this.pMouseDown = false;
			this.pMousePrecise = new Vector2();
			this.pMousePosition = new Point(0, 0);

			this.pScale = new Vector2(this.pSize.X / 320f, this.pSize.Y / 240f);
			this.pWindow.ClientSizeChanged += this.OnWindowClientBoundsChanged;

			this.pMainMenu = new PainectMenu(this);
			this.pMainMenu.Visible = false;

			this.pQuickMenu = new QuickMenu(this);
			this.pQuickMenu.Visible = false;
			this.pQuickMenu.Location = new Point(
					(this.pBounds.Width - this.pQuickMenu.Size.X) / 2,
					(this.pBounds.Height - this.pQuickMenu.Size.Y) / 2);

			Color[] buffer = DKE_Kinect.Entwicklung.Grafik.ControlManaging.ColorMenu.GetColorsFromPalette();
			Color[] palette = new Color[buffer.Length / 3];

			for (int i = 0; i < buffer.Length / 3; i++)
				palette[i] = buffer[i];

			this.pColorMenu = new ColorMenu(this, palette, 8);
			this.pColorMenu.Visible = false;
			this.pColorMenu.Size = this.pSize;

           // this.pProjectMenu = new ProjectMenu(this, "../../../../DKE_KinectContent/projects/", 8);
            this.pProjectMenu = new ProjectMenu(this, 8);
            this.pProjectMenu.Visible = false;
            this.pProjectMenu.Size = this.pSize;
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gibt das Hauptmenü für den Container an.
		/// </summary>
		public PainectMenu MainMenu { get { return this.pMainMenu; } }
		/// <summary>
		/// Gibt das Quickmenü für den Container an.
		/// </summary>
		public QuickMenu QuickMenu { get { return this.pQuickMenu; } }
		/// <summary>
		/// Gibt das Colormenü für den Container an.
		/// </summary>
		public ColorMenu ColorMenu { get { return this.pColorMenu; } }
        /// <summary>
        /// Gibt das Projectmenü für den Container an.
        /// </summary>
        public ProjectMenu ProjectMenu { get { return this.pProjectMenu; } }

		/// <summary>
		/// Gibt die Position des Steuerelements an oder legt diese fest.
		/// </summary>
		public Point Location { get { return this.pLocation; } }
		/// <summary>
		/// Gibt die Größe des Steuerelements an oder legt diese fest.
		/// X entspricht der Breite.
		/// Y entspricht der Höhe.
		/// </summary>
		public Point Size { get { return this.pSize; } }
		/// <summary>
		/// Gibt die Grenzen des Steuerelements an oder legt diese fest.
		/// </summary>
		public Rectangle Bounds { get { return this.pBounds; } }

		/// <summary>
		/// Gibt die Grenzen des Bildschirms an.
		/// </summary>
		public Rectangle? RelativeScreenBounds { get { return this.pBounds; } }
		/// <summary>
		/// Gibt die Grenzen des Bildschirms an.
		/// </summary>
		public Rectangle? VisibleScreenBounds { get { return this.Bounds; } }

		/// <summary>
		/// Gibt alle untergeordneten Steuerelemente an.
		/// </summary>
		public ControlCollection Controls { get { return this.pControls; } }

		#endregion

		#region Methods

		/// <summary>
		/// TODO
		/// </summary>
		public void Refresh ()
		{
			foreach (Control ctrl in this.pControls)
				ctrl.Refresh();
		}

		/// <summary>
		/// Ruft für alle untergeordneten Steuerelemente die Methode Update auf.
		/// </summary>
		/// <param name="gameTime">Zeitinfo.</param>
		public void Update (GameTime gameTime)
		{
			foreach (Control ctrl in this.pControls)
				ctrl.Update(gameTime);
		}

		/// <summary>
		/// Ruft für alle untergeordneten Steuerelemente die Methode Draw auf.
		/// </summary>
		/// <param name="gameTime">Zeitinfo.</param>
		/// <param name="batch"></param>
		public void Draw (GameTime gameTime, SpriteBatch batch)
		{
			foreach (Control ctrl in this.pControls)
				ctrl.Draw(gameTime, batch);

			//    Rectangle curRect = ContainerRessources.Cursor.Bounds;
			//    batch.Draw(ContainerRessources.Cursor,
			//new Rectangle(this.pMousePosition.X, this.pMousePosition.Y, curRect.Width, curRect.Height), null, Color.White, 0.0f,
			//new Vector2(curRect.Width / 2, curRect.Height / 2), SpriteEffects.None, 0);
		}

		internal Control FindControlAt (Point Location)
		{
			Rectangle compareTo;
			IContainer last = this;
			bool nextLayer;

			do
			{
				nextLayer = false;

				for (int i = last.Controls.Count - 1; i >= 0; i--)
				{
					IContainer next = last.Controls[i];

					if (!next.VisibleScreenBounds.HasValue)
						continue;

					compareTo = next.VisibleScreenBounds.Value;
					if ((Location.X >= compareTo.Left) && (Location.X < compareTo.Right) && (Location.Y >= compareTo.Top) && (Location.Y < compareTo.Bottom))
					{
						last = next;
						nextLayer = true;
						break;
					}
				}
			} while (nextLayer);

			return last as Control;
		}

		private Point NormMouseValue (Point? Location)
		{
			Point destLoc;

			if (Location.HasValue)
				destLoc = Location.Value;
			else
				return this.pMousePosition;

			// x - Koordinate anpassen:
			if (destLoc.X < 0)
				destLoc.X = 0;
			else if (destLoc.X >= this.pSize.X)
				destLoc.X = this.pSize.X;

			// y - Koordinate anpassen:
			if (destLoc.Y < 0)
				destLoc.Y = 0;
			else if (destLoc.Y >= this.pSize.Y)
				destLoc.Y = this.pSize.Y;

			return destLoc;
		}
		/// <summary>
		/// Simuliert ein Ereignis auf eines der untergeordneten Steuerelemente, bei dem die Maus geklickt wird.
		/// </summary>
		public void PerformMouseClick ()
		{
			Control mouseOn = this.FindControlAt(this.pMousePosition);

			if (mouseOn != null)
				mouseOn.PerformMouseClick();
		}
		/// <summary>
		/// Simuliert ein Ereignis auf eines der untergeordneten Steuerelemente, bei dem die Maus heruntergedrückt wird.
		/// </summary>
		public void PerformMouseDown ()
		{
			if (this.pMouseDown)
				throw new InvalidOperationException("Eine Maus muss erst wieder losgelassen werden,damit sie wieder gedrückt werden kann.");

			Control mouseOn = this.FindControlAt(this.pMousePosition);
			if (mouseOn != null)
			{
				this.pMouseDown = true;
				this.pPrevControl = mouseOn;
				mouseOn.PerformMouseDown(this.pMousePosition);
			}
		}
		/// <summary>
		/// Simuliert ein Ereignis auf eines der untergeordneten Steuerelemente, bei dem die Maus losgelassen wird.
		/// </summary>
		public void PerformMouseUp ()
		{
			if (!this.pMouseDown)
				throw new InvalidOperationException("Die Maus kann nur losgelassen werden, wenn sie bereits heruntergedrückt wurde.");

			this.pMouseDown = false;
			this.pPrevControl.PerformMouseUp(this.pMousePosition);
		}
		/// <summary>
		/// Simuliert ein Ereignis auf eines der untergeordneten Steuerelemente, bei dem die Maus bewegt wird.
		/// </summary>
		/// <param name="DistanceChange">Die Veränderung des Cursors um die angegebene Position.</param>
		public void PerformMouseMove (Vector2 DistanceChange)
		{
			this.pMousePrecise = Vector2.Add(this.pMousePrecise, -DistanceChange);
			this.pMousePosition = DKE_Kinect.Entwicklung.Core.Convert.Vector2ToPoint(Vector2.Multiply(this.pMousePrecise, this.pScale));

			if (this.pMouseDown)
				this.pPrevControl.PerformMouseMove(Location);
			else
			{
				//this.pMousePosition = this.NormMouseValue(Location);
				Control mouseOn = this.FindControlAt(this.pMousePosition);

				if (mouseOn != null)
				{
					if (mouseOn.Equals(this.pPrevControl))
						this.pPrevControl.PerformMouseMove(this.pMousePosition);
					else
					{
						if (this.pPrevControl != null)
							this.pPrevControl.PerformMouseLeave();

						mouseOn.PerformMouseEnter();
					}
				}
				else
				{
					if (this.pPrevControl != null)
						this.pPrevControl.PerformMouseLeave();
				}

				this.pPrevControl = mouseOn;
			}
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="Location"></param>
		public void PerformMouseMoveTo (Vector2 Location)
		{
			this.pMousePrecise = Location;
			this.pMousePosition = DKE_Kinect.Entwicklung.Core.Convert.Vector2ToPoint(Location);

			if (this.pMouseDown)
				this.pPrevControl.PerformMouseMove(this.pMousePosition);
			else
			{
				//this.pMousePosition = this.NormMouseValue(Location);
				Control mouseOn = this.FindControlAt(this.pMousePosition);

				if (mouseOn != null)
				{
					if (mouseOn.Equals(this.pPrevControl))
						this.pPrevControl.PerformMouseMove(this.pMousePosition);
					else
					{
						if (this.pPrevControl != null)
							this.pPrevControl.PerformMouseLeave();

						mouseOn.PerformMouseEnter();
					}
				}
				else
				{
					if (this.pPrevControl != null)
						this.pPrevControl.PerformMouseLeave();
				}

				this.pPrevControl = mouseOn;
			}
		}

		private void OnWindowClientBoundsChanged (object Sender, EventArgs e)
		{
			this.pBounds = new Rectangle(0, 0, this.pWindow.ClientBounds.Width, this.pWindow.ClientBounds.Height);
			this.pSize = new Point(this.pWindow.ClientBounds.Width, this.pWindow.ClientBounds.Height);

			this.OnSizeChanged(EventArgs.Empty);
		}

		// Event Handling
		/// <summary>
		/// Löst das Ereignis SizeChanged aus.
		/// </summary>
		/// <param name="e"></param>
		private void OnSizeChanged (EventArgs e)
		{
			this.Refresh();

			this.pMainMenu.Size = this.pSize;
			this.pColorMenu.Size = this.pSize;
            this.pProjectMenu.Size = this.pSize;

			this.pQuickMenu.Location = new Point(
					(this.pBounds.Width - this.pQuickMenu.Size.X) / 2,
					(this.pBounds.Height - this.pQuickMenu.Size.Y) / 2);

			this.pScale = new Vector2(this.pSize.X / 320f, this.pSize.Y / 240f);

			if (this.SizeChanged != null)
				this.SizeChanged(this, e);
		}

		// System.object Overrides
		/// <summary>
		/// Gibt einen System.string zurück, der das akutelle Objekt darstellt.
		/// </summary>
		/// <returns></returns>
		public override string ToString ()
		{
			return "ContainerManager";
		}

		// Miscellaneous
		/// <summary>
		/// Klont eine Textur.
		/// </summary>
		/// <param name="Texture">Die zu klonende Textur.</param>
		/// <returns></returns>
		public Texture2D Clone (Texture2D Texture)
		{
			if (Texture == null)
				return null;

			int[] pixels = new int[Texture.Width * Texture.Height];
			Texture.GetData<int>(pixels);

			Texture2D clone = new Texture2D(this.pDevice, Texture.Width, Texture.Height);

			clone.SetData<int>(pixels);

			return clone;
		}

		#endregion

		/// <summary>
		/// Tritt ein, wenn die Größe des Steuerelements geändert wurde.
		/// </summary>
		public event EventHandler SizeChanged;
	}

	/// <summary>
	/// Verwaltet Bilder und andere Ressourcen für die Darstellung der Komponenten.
	/// </summary>
	internal static class ContainerRessources
	{
		#region Fields

		private static Texture2D[] pBigButton;
		private static Texture2D[]
				pStandardButtons,
				pStandardButtonsHover,
				pStandardButtonQM,
				pStandardButtonQMHover,
				pStandardButtonQMSelected,
				pStandardButtonQMSelectedHover,
				pStandardButtonBM,
				pStandardButtonBMHover,
                pStandardButtonPBM,
				pStandardButtonPBMHover;

		private static Texture2D pLabel;

		private static Texture2D[] pIcons;

		private static Texture2D pCursor;

		#endregion

		internal static Texture2D[] GetButtons (ButtonType Type)
		{
			switch (Type)
			{
				// Hauptmenü Standard:
				case ButtonType.MainMenuStatic:
					if (ContainerRessources.pStandardButtons == null)
					{
						ContainerRessources.pStandardButtons = new Texture2D[8];
						for (int i = 0; i < ContainerRessources.pStandardButtons.Length; )
							ContainerRessources.pStandardButtons[i] = ContentManager.loadContent<Texture2D>(string.Format("textures/Buttons/Static/Button {0}", ++i));
					}

					return ContainerRessources.pStandardButtons;

				// Hauptmenü Ausgewählt:
				case ButtonType.MainMenuHover:
					if (ContainerRessources.pStandardButtonsHover == null)
					{
						ContainerRessources.pStandardButtonsHover = new Texture2D[8];
						for (int i = 0; i < ContainerRessources.pStandardButtonsHover.Length; )
							ContainerRessources.pStandardButtonsHover[i] = ContentManager.loadContent<Texture2D>(string.Format("textures/Buttons/Hover/Button {0} Hover", ++i));
					}

					return ContainerRessources.pStandardButtonsHover;

				// Quickmenü Standard:
				case ButtonType.QuickMenuStatic:
					if (ContainerRessources.pStandardButtonQM == null)
					{
						ContainerRessources.pStandardButtonQM = new Texture2D[6];
						for (int i = 0; i < ContainerRessources.pStandardButtonQM.Length; )
							ContainerRessources.pStandardButtonQM[i] = ContentManager.loadContent<Texture2D>(string.Format("textures/Buttons/Static/QM Button {0}", ++i));
					}

					return ContainerRessources.pStandardButtonQM;

				// Quickmenü Ausgewählt:
				case ButtonType.QuickMenuHover:
					if (ContainerRessources.pStandardButtonQMHover == null)
					{
						ContainerRessources.pStandardButtonQMHover = new Texture2D[6];
						for (int i = 0; i < ContainerRessources.pStandardButtonQMHover.Length; )
							ContainerRessources.pStandardButtonQMHover[i] = ContentManager.loadContent<Texture2D>(string.Format("textures/Buttons/Hover/QM Button {0} Hover", ++i));
					}

					return ContainerRessources.pStandardButtonQMHover;

				// Quickmenü Aktiviert:
				case ButtonType.QuickMenuSelected:
					if (ContainerRessources.pStandardButtonQMSelected == null)
					{
						ContainerRessources.pStandardButtonQMSelected = new Texture2D[6];
						for (int i = 0; i < ContainerRessources.pStandardButtonQMSelected.Length; )
							ContainerRessources.pStandardButtonQMSelected[i] = ContentManager.loadContent<Texture2D>(string.Format("textures/Buttons/Static/QM Button {0} Selected", ++i));
					}

					return ContainerRessources.pStandardButtonQMSelected;

				// Quickmenü Aktiviert ausgewählt:
				case ButtonType.QuickMenuSelectedHover:
					if (ContainerRessources.pStandardButtonQMSelectedHover == null)
					{
						ContainerRessources.pStandardButtonQMSelectedHover = new Texture2D[6];
						for (int i = 0; i < ContainerRessources.pStandardButtonQMSelectedHover.Length; )
							ContainerRessources.pStandardButtonQMSelectedHover[i] = ContentManager.loadContent<Texture2D>(string.Format("textures/Buttons/Hover/QM Button {0} Selected Hover", ++i));
					}

					return ContainerRessources.pStandardButtonQMSelectedHover;

				// Big Button:
				case ButtonType.BigButton:
					if (ContainerRessources.pBigButton == null)
						ContainerRessources.pBigButton = new Texture2D[] { ContentManager.loadContent<Texture2D>("textures/Buttons/BigButton") };

					return ContainerRessources.pBigButton;


				// Bubblemenü Standard:
				case ButtonType.BubbleMenuStatic:
					if (ContainerRessources.pStandardButtonBM == null)
						ContainerRessources.pStandardButtonBM = new Texture2D[] { ContentManager.loadContent<Texture2D>("textures/Buttons/Static/Bubble Button Static") };

					return ContainerRessources.pStandardButtonBM;

				// Bubblemenü ausgewählt:
				case ButtonType.BubbleMenuHover:
					if (ContainerRessources.pStandardButtonBMHover == null)
						ContainerRessources.pStandardButtonBMHover = new Texture2D[] { ContentManager.loadContent<Texture2D>("textures/Buttons/Hover/Bubble Button Hover") };

					return ContainerRessources.pStandardButtonBMHover;

                // Projekt-Bubblemenü Standard:
                case ButtonType.PBubbleMenuStatic:
                    if (ContainerRessources.pStandardButtonPBM == null)
                        ContainerRessources.pStandardButtonPBM = new Texture2D[] { ContentManager.loadContent<Texture2D>("textures/Buttons/Static/Project Button Static") };

                    return ContainerRessources.pStandardButtonPBM;

                // Projekt-Bubblemenü ausgewählt:
                case ButtonType.PBubbleMenuHover:
                    if (ContainerRessources.pStandardButtonPBMHover == null)
                        ContainerRessources.pStandardButtonPBMHover = new Texture2D[] { ContentManager.loadContent<Texture2D>("textures/Buttons/Hover/Project Button Hover") };

                    return ContainerRessources.pStandardButtonPBMHover;
				default:
					return null;
			}
		}

		internal static Texture2D[] GetIcons ()
		{
			if (ContainerRessources.pIcons == null)
			{
				ContainerRessources.pIcons = new Texture2D[12];
				for (int i = 0; i < ContainerRessources.pIcons.Length; )
					ContainerRessources.pIcons[i] = ContentManager.loadContent<Texture2D>(string.Format("textures/Icons/{0}", i++));
			}

			return ContainerRessources.pIcons;
		}

		internal static ButtonType Pendant (ButtonType Type)
		{
			switch (Type)
			{
				case ButtonType.MainMenuStatic:
					return ButtonType.MainMenuHover;

				case ButtonType.MainMenuHover:
					return ButtonType.MainMenuStatic;

				case ButtonType.QuickMenuStatic:
					return ButtonType.QuickMenuHover;

				case ButtonType.QuickMenuHover:
					return ButtonType.QuickMenuStatic;

				case ButtonType.QuickMenuSelected:
					return ButtonType.QuickMenuSelectedHover;

				case ButtonType.QuickMenuSelectedHover:
					return ButtonType.QuickMenuSelected;

				case ButtonType.BigButton:
					return ButtonType.BigButton;

				case ButtonType.BubbleMenuStatic:
					return ButtonType.BubbleMenuHover;

				case ButtonType.BubbleMenuHover:
					return ButtonType.BubbleMenuStatic;

				default:
					throw new ArgumentException();
			}
		}

		internal static Texture2D GetLabel ()
		{
			if (ContainerRessources.pLabel == null)
				ContainerRessources.pLabel = ContentManager.loadContent<Texture2D>("textures/Buttons/Label");

			return ContainerRessources.pLabel;
		}

		internal static Texture2D Cursor
		{
			get
			{
				if (ContainerRessources.pCursor == null)
					ContainerRessources.pCursor = ContentManager.loadContent<Texture2D>("textures/Cursor");

				return ContainerRessources.pCursor;
			}
		}

		internal static SpriteFont GetFont (string FontName)
		{
			try
			{
				return ContentManager.loadContent<SpriteFont>("objects/Fonts/" + FontName);
			}
			catch { return null; }
		}
	}
}
